/**
 * 获取数据的类型名称
 * @param {*} target
 * @returns {string} 类型名称
 */
export const getType = (target) => Object.prototype.toString.call(target).slice(8, -1);

/**
 * 判断是否定义
 * @param {any} target
 * @returns {boolean}
 */
export const isDef = (target) => target !== undefined && target !== null;

/**
 * 判断是否是Number类型
 * @param {any} target
 * @returns {boolean}
 */
export const isNumber = (target) => getType(target) === 'Number';

/**
 * 保留小数
 * @param {number} num
 * @param {number} maxDigit
 * @returns
 */
export const toFixed = (num, maxDigit = 2) => parseFloat(Number(num).toFixed(maxDigit));

/**
 * 控制精度
 * @param {number} num
 * @returns
 */
export const precision = (num) => toFixed(num, 10);

/**
 * 携带px
 * @param {number|string} value
 * @returns
 */
export const toPx = (value) => {
  const v = parseFloat(`${value}`);
  return Object.is(v, NaN) ? '' : `${v}px`;
};

/**
 * 生成唯一标识
 * @returns uuid
 */
export const uuid = () => {
  let d = new Date().getTime();
  if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
    d += performance.now(); // use high-precision timer if available
  }
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    const r = (d + Math.random() * 16) % 16 | 0;
    d = Math.floor(d / 16);
    return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
  });
};

/**
 * 深度克隆数据
 * @param {*} data
 * @returns {*} 深度克隆后的数据
 */
export const cloneDeep = (data) => {
  const type = getType(data);
  if (type === 'Array') return data.reduce((arr, item) => (arr.push(cloneDeep(item)), arr), []);
  if (type === 'Object') return Object.keys(data).reduce((obj, key) => ((obj[key] = cloneDeep(data[key])), obj), {});
  return data;
};

/**
 * 获取画布元素相对视口的坐标
 * @param {HTMLCanvasElement} canvas
 * @returns {{x:number, y:number}}
 */
export const getOffsetXY = (canvas) => {
  const style = getComputedStyle(canvas);
  // 各个方向的边框长度
  const borderLeft = parseFloat(style['border-left-width']);
  const borderTop = parseFloat(style['border-top-width']);
  const paddingLeft = parseFloat(style['padding-left']);
  const paddingTop = parseFloat(style['padding-top']);
  // 获取元素的大小及其相对于视口的位置
  const rect = canvas.getBoundingClientRect();
  const x = rect.left + borderLeft + paddingLeft; // 去除 borderLeft paddingLeft 后的坐标
  const y = rect.top + borderTop + paddingTop; // 去除 borderLeft paddingLeft 后的坐标
  return { x, y };
};

/**
 * 获取画布元素的全局相对坐标
 * @param {HTMLCanvasElement} canvas
 * @param {MouseEvent} e
 * @returns {{x:number, y:number}}
 */
export const getOffsetCoord = (canvas, e) => {
  let { x, y } = e;
  const style = getComputedStyle(canvas);
  // 宽高
  const cssWidth = parseFloat(style['width']);
  const cssHeight = parseFloat(style['height']);
  // 计算缩放值
  const scaleX = canvas.width / cssWidth; // 水平方向的缩放因子
  const scaleY = canvas.height / cssHeight; // 垂直方向的缩放因子
  // 获取画布元素相对视口的坐标
  const offsetXY = getOffsetXY(canvas);
  x -= offsetXY.x;
  y -= offsetXY.y;
  x *= scaleX; // 修正水平方向的坐标
  y *= scaleY; // 修正垂直方向的坐标
  return { x, y };
};

/**
 * 获取点以原点旋转及缩放后的坐标
 * @param {{x:number, y:number}} target 目标点
 * @param {{x:number, y:number}} origin 原点
 * @param {number} rotation 旋转角度
 * @param {number} scale 缩放值
 * @returns {{x:number, y:number}}
 */
export const transformedCoord = (target, origin, rotation = 0, scale = 1) => {
  const radian = rotation * (Math.PI / 180);
  const cosAngle = Math.cos(radian);
  const sinAngle = Math.sin(radian);
  // 获取相对于原点的坐标
  const offsetX = (target.x - origin.x) * scale;
  const offsetY = (target.y - origin.y) * scale;
  // 得到旋转及缩放后的坐标
  const rotatedX = origin.x + offsetX * cosAngle - offsetY * sinAngle;
  const rotatedY = origin.y + offsetX * sinAngle + offsetY * cosAngle;
  return { x: rotatedX, y: rotatedY };
};

/**
 * 判断点是否在圆形内
 * @param {{x:number, y:number}} point 点
 * @param {{x:number, y:number}} center 圆心
 * @param {number} radius 半径
 * @returns
 */
export const isPointInsideCircle = (point, center, radius) => {
  // 计算点到圆心的距离
  const distance = dist(point, center);
  // 如果距离小于等于半径，则点在圆形内
  return distance <= radius;
};

/**
 * 判断两条线段是否相交
 * @param {{o:{x:number,y:number},d:{x:number,y:number}}} segment1
 * @param {{o:{x:number,y:number},d:{x:number,y:number}}} segment2
 * @returns
 */
export const segmentsIntersect = (segment1, segment2) => {
  const { o: A, d: B } = segment1;
  const { o: C, d: D } = segment2;
  // 线段AB的法线N1
  const nx1 = B.y - A.y;
  const ny1 = A.x - B.x;

  // 线段CD的法线N2
  const nx2 = D.y - C.y;
  const ny2 = C.x - D.x;

  // 两条法线做叉乘, 如果结果为0, 说明线段AB和线段CD平行或共线，不相交
  const denominator = nx1 * ny2 - ny1 * nx2;
  if (denominator === 0) return false;

  // 在法线N1上的投影
  const distA_N1 = nx1 * A.x + ny1 * A.y;
  const distC_N1 = nx1 * C.x + ny1 * C.y - distA_N1;
  const distD_N1 = nx1 * D.x + ny1 * D.y - distA_N1;
  // 点C投影和点D投影在点A投影同侧 (对点在线段上的情况，当作不相交处理)
  if (distC_N1 * distD_N1 >= 0) return false;

  // 在法线N2上的投影
  const distC_N2 = nx2 * C.x + ny2 * C.y;
  const distA_N2 = nx2 * A.x + ny2 * A.y - distC_N2;
  const distB_N2 = nx2 * B.x + ny2 * B.y - distC_N2;

  // 点A投影和点B投影在点C投影同侧 (对点在线段上的情况，当作不相交处理)
  if (distA_N2 * distB_N2 >= 0) return false;

  // 计算交点坐标
  const fraction = distA_N2 / denominator;
  const dx = fraction * ny1;
  const dy = -fraction * nx1;
  return { x: A.x + dx, y: A.y + dy };
};

/**
 * 转换为0~359的角度值
 * @param {number} rotation 角度
 * @returns
 */
export const normalizeRotation = (rotation) => {
  if (!isFinite(rotation)) return 0;
  rotation %= 360;
  if (rotation < 0) rotation += 360;
  return rotation;
};

/**
 * 获取两点向量的旋转角度
 * @param {{x:number, y:number}} origin 原点
 * @param {{x:number, y:number}} target 目标点
 * @returns
 */
export const vectorRotation = (origin, target) => {
  const radian = Math.atan2(target.y - origin.y, target.x - origin.x);
  return normalizeRotation(radian * (180 / Math.PI));
};

/**
 * 根据两点向量的旋转角度获取两点所在直线的斜率
 * @param {number} vectorRotation 两点向量的旋转角度
 * @returns
 */
export const vectorRotationSlope = (vectorRotation) => {
  if ((vectorRotation + 90) % 180 === 0) return Infinity;
  return Math.tan(vectorRotation * (Math.PI / 180));
};

/**
 * 获取两点的中点
 * @param {{x:number, y:number}} point1
 * @param {{x:number, y:number}} point2
 * @returns
 */
export const midpoint = (point1, point2) => {
  return { x: (point1.x + point2.x) / 2, y: (point1.y + point2.y) / 2 };
};

/**
 * 计算两点之间的距离
 * @param {{x:number, y:number}} point1
 * @param {{x:number, y:number}} point2
 * @returns
 */
export const dist = (point1, point2) => {
  return Math.hypot(point1.x - point2.x, point1.y - point2.y);
};

/**
 * 获取两点所在直线的斜率
 * @param {{x:number, y:number}} point1
 * @param {{x:number, y:number}} point2
 * @returns
 */
export const slope = (point1, point2) => {
  // 控制精度，减小坐标计算误差
  return precision(point1.y - point2.y) / precision(point1.x - point2.x);
};

/**
 * 根据两点及其斜率求两直线交点坐标
 * y−y1​=k1​(x−x1​)
 * y−y2​=k2​(x−x2​)
 * x=(​k1​x1​−y1​−k2​x2​+y2)​​/(k1​−k2)
 * @param {{x:number, y:number}} point1
 * @param {number} k1
 * @param {{x:number, y:number}} point2
 * @param {number} k2
 * @returns
 */
export const intersectionPoint = (point1, k1, point2, k2) => {
  const { x: x1, y: y1 } = point1;
  const { x: x2, y: y2 } = point2;
  // 斜率为NaN
  if (isNaN(k1) || isNaN(k2)) throw new Error('其中一个斜率是NaN');
  // 两直线平行
  else if (k1 === k2) throw new Error('斜率相等，两直线平行，无交点');
  // 直线1平行于y轴
  else if (!isFinite(k1)) return { x: x1, y: k2 * (x1 - x2) + y2 };
  // 直线2平行于y轴
  else if (!isFinite(k2)) return { x: x2, y: k1 * (x2 - x1) + y1 };
  // 直线1平行于x轴
  else if (k1 === 0) return { x: (y1 - y2) / k2 + x2, y: y1 };
  // 直线2平行于x轴
  else if (k2 === 0) return { x: (y2 - y1) / k1 + x1, y: y2 };
  // 一般情况
  const x = (k1 * x1 - y1 - k2 * x2 + y2) / (k1 - k2);
  const y = k1 * (x - x1) + y1;
  return { x, y };
};

/**
 * 根据点及其斜率求与该点相距一定距离的两点
 * @param {{x:number, y:number}} point
 * @param {number} k
 * @param {number} distance
 * @param {{x:number, y:number}|number} guide 引导点/引导角度
 * @returns
 */
export const distancePoints = (point, k, distance, guide = undefined) => {
  const { x, y } = point;
  let point1, point2;
  // 直线平行于x轴
  if (k === 0) {
    point1 = { x: x + distance, y: y };
    point2 = { x: x - distance, y: y };
  }
  // 直线平行于y轴
  else if (!isFinite(k)) {
    point1 = { x: x, y: y + distance };
    point2 = { x: x, y: y - distance };
  }
  // 一般情况
  else {
    const b = y - k * x;
    const delta = Math.sqrt((distance * distance) / (1 + k * k));
    point1 = { x: x + delta, y: k * (x + delta) + b };
    point2 = { x: x - delta, y: k * (x - delta) + b };
  }
  // 未传入引导点/引导角度，直接返回两点
  if (!isDef(guide)) return [point1, point2];
  // 传入引导点/引导角度 获取 引导角度
  const guideRotation = typeof guide === 'number' ? guide : vectorRotation(point, guide);
  // 0下标的点为与 引导点/引导角度 同一方向上的点
  if (Math.abs(guideRotation - vectorRotation(point, point1)) < 90) return [point1, point2];
  else return [point2, point1];
};

/**
 * 计算两个矩形之间的距离
 * @param {{left:number, right:number, top:number, bottom:number}} rect1
 * @param {{left:number, right:number, top:number, bottom:number}} rect2
 * @returns
 */
export const calcRectDist = (rect1, rect2) => {
  // rect1 与 rect2 水平方向碰撞
  const horizontalColliding = !(rect1.top > rect2.bottom || rect1.bottom < rect2.top);
  // rect1 与 rect2 垂直方向碰撞
  const verticalColliding = !(rect1.right < rect2.left || rect1.left > rect2.right);
  // rect1 与 rect2 碰撞 距离为 四条边距离平均值
  if (horizontalColliding && verticalColliding) {
    return (
      (Math.abs(rect1.left - rect2.left) +
        Math.abs(rect1.right - rect2.right) +
        Math.abs(rect1.top - rect2.top) +
        Math.abs(rect1.bottom - rect2.bottom)) /
      4
    );
  }
  // rect1 与 rect2 水平方向距离
  const horizontalDist = Math.abs(Math.max(rect1.left, rect2.left) - Math.min(rect1.right, rect2.right));
  // rect1 与 rect2 垂直方向距离
  const verticalDist = Math.abs(Math.max(rect1.top, rect2.top) - Math.min(rect1.bottom, rect2.bottom));
  // rect1 与 rect2 只有水平方向碰撞 距离为 水平方向距离
  if (horizontalColliding && !verticalColliding) return horizontalDist;
  // rect1 与 rect2 只有垂直方向碰撞 距离为 垂直方向距离
  if (verticalColliding && !horizontalColliding) return verticalDist;
  // rect1 与 rect2 水平、垂直方向都未碰撞 距离为 中间部分矩形对角线长度
  return Math.hypot(horizontalDist, verticalDist);
};
